package com.acme.bankapp.service.bank;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.StringTokenizer;
import java.util.regex.Pattern;

import com.acme.bankapp.domain.bank.Account;
import com.acme.bankapp.domain.bank.Bank;
import com.acme.bankapp.domain.bank.CheckingAccount;
import com.acme.bankapp.domain.bank.Client;
import com.acme.bankapp.domain.bank.Gender;
import com.acme.bankapp.domain.bank.SavingAccount;
import com.acme.bankapp.domain.bank.exception.AccountAlreadyAssignedException;
import com.acme.bankapp.domain.bank.exception.ClientExistsException;
import com.acme.bankapp.service.bank.exception.ClientLineParseException;
import com.acme.bankapp.service.bank.exception.InvalidFeedFormatException;

public class BankDataLoaderService {
	private static final String TOKEN_DELIM = ";";
	private static final String PARAM_DELIM_REGEX = "=";

	public static Bank parseFile(File feedFile) throws IOException,
			InvalidFeedFormatException {
		Bank bank = new Bank();
		parseFile(feedFile, bank);
		return bank;
	}

	public static void parseFile(File feedFile, Bank bank) throws IOException,
			InvalidFeedFormatException {
		try (BufferedReader reader = new BufferedReader(
				new FileReader(feedFile))) {
			String line;
			while ((line = reader.readLine()) != null) {
				parseAndApplyClientLine(line, bank);
			}
		} catch (ClientLineParseException ex) {
			throw new InvalidFeedFormatException(feedFile);
		}
	}

	/**
	 * Parses client data from list and adds client to bank.
	 * 
	 * @param line
	 *            parsed line
	 * @param bank
	 *            bank instance
	 * @throws ClientLineParseException
	 */
	public static void parseAndApplyClientLine(String line, Bank bank)
			throws ClientLineParseException {
		StringTokenizer tokenizer = new StringTokenizer(line, TOKEN_DELIM);
		AccountType accountType = null;
		Double balance = null;
		Double overdraft = null;
		String clientName = null;
		Gender gender = null;
		while (tokenizer.hasMoreTokens()) {
			Pattern pattern = Pattern.compile(PARAM_DELIM_REGEX);
			String[] parts = pattern.split(tokenizer.nextToken());
			if (parts.length != 2) {
				throw new ClientLineParseException();
			}
			String leftPart = parts[0];
			String rightPart = parts[1];
			switch (leftPart) {
			case "accounttype":
				if (rightPart.equals("c")) {
					accountType = AccountType.CHECKING_ACCOUNT;
				} else if (rightPart.equals("s")) {
					accountType = AccountType.SAVING_ACCOUNT;
				} else {
					throw new ClientLineParseException();
				}
				break;
			case "balance":
				try {
					balance = Double.parseDouble(rightPart);
				} catch (NumberFormatException ex) {
					throw new ClientLineParseException();
				}
				break;
			case "overdraft":
				try {
					overdraft = Double.parseDouble(rightPart);
				} catch (NumberFormatException ex) {
					throw new ClientLineParseException();
				}
				break;
			case "name":
				clientName = rightPart;
				break;
			case "gender":
				if (rightPart.equals("m")) {
					gender = Gender.MALE;
				} else if (rightPart.equals("f")) {
					gender = Gender.FEMALE;
				} else {
					throw new ClientLineParseException();
				}
				break;
			}
		}
		addAccountToBank(bank, accountType, balance, overdraft, clientName,
				gender);
	}

	private static void addAccountToBank(Bank bank, AccountType accountType,
			Double balance, Double overdraft, String clientName, Gender gender)
			throws ClientLineParseException {
		if (accountType != null
				&& balance != null
				&& (accountType == AccountType.SAVING_ACCOUNT || accountType == AccountType.CHECKING_ACCOUNT
						&& overdraft != null) && clientName != null
				&& gender != null) {
			Client client = new Client(gender, clientName);
			Account account = null;
			switch (accountType) {
			case CHECKING_ACCOUNT:
				account = new CheckingAccount(balance, overdraft);
				break;
			case SAVING_ACCOUNT:
				account = new SavingAccount(balance);
				break;
			}
			try {
				try {
					bank.addClient(client);
				} catch (ClientExistsException ex) {
					client = bank.locateClient(client);
				}
				client.addAccount(account);
			} catch (AccountAlreadyAssignedException e) {
				throw new ClientLineParseException();
			}
		} else {
			throw new ClientLineParseException();
		}
	}
}

enum AccountType {
	CHECKING_ACCOUNT, SAVING_ACCOUNT
}